/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_input.cpp - device button input management
//

// TODO:
// - IN_Init


#include "client.h"


cvar_t *					in_alwaysRun;
cvar_t *					in_toggleRun;
cvar_t *					in_toggleZoom;
cvar_t *					in_angleSpeedKey;
cvar_t *					in_pitchSpeed;
cvar_t *					in_yawSpeed;
cvar_t *					in_freeLook;
cvar_t *					in_forwardSpeed;
cvar_t *					in_sideSpeed;
cvar_t *					in_upSpeed;
cvar_t *					in_mouse;
cvar_t *					in_controller;

cvar_t *					m_showRate;
cvar_t *					m_smooth;
cvar_t *					m_sensitivity;
cvar_t *					m_accel;
cvar_t *					m_forward;
cvar_t *					m_side;
cvar_t *					m_pitch;
cvar_t *					m_yaw;

cvar_t *					c_deviceName;


/*
 ==============================================================================

 KEY BUTTONS

 Continuous button event tracking is complicated by the fact that two different
 input sources (say, left mouse button and the control key) can both press the
 same button, but the button should only be released when both of the pressing
 keys have been released.

 When a key event issues a key button command, it sends its key number as a
 parameter so it can be matched up with the release.

 It also sends the time the event happened, which allows exact control even at
 low framerates when the down and up events may both get queued in the same
 frame.

 ==============================================================================
*/

struct keyButtonCmd_t {
	const char *			name;
	keyButton_t *			keyButton;
};

static keyButtonCmd_t		in_keyButtonCmds[] = {
	{"_moveForward",			&in_moveForward},
	{"_moveBack",				&in_moveBack},
	{"_moveRight",				&in_moveRight},
	{"_moveLeft",				&in_moveLeft},
	{"_moveUp",					&in_moveUp},
	{"_moveDown",				&in_moveDown},
	{"_turnRight",				&in_turnRight},
	{"_turnLeft",				&in_turnLeft},
	{"_lookUp",					&in_lookUp},
	{"_lookDown",				&in_lookDown},
	{"_speed",					&in_speed},
	{"_strafe",					&in_strafe},
	{"_voiceChat",				&in_voiceChat},
	{"_attack",					&in_buttons[KB_ATTACK]},
	{"_use",					&in_buttons[KB_USE]},
	{"_sprint",					&in_buttons[KB_SPRINT]},
	{"_zoom",					&in_buttons[KB_ZOOM]},
	{"_button4",				&in_buttons[4]},
	{"_button5",				&in_buttons[5]},
	{"_button6",				&in_buttons[6]},
	{"_button7",				&in_buttons[7]},
	{"_button8",				&in_buttons[8]},
	{"_button9",				&in_buttons[9]},
	{"_button10",				&in_buttons[10]},
	{"_button11",				&in_buttons[11]},
	{"_button12",				&in_buttons[12]},
	{"_button13",				&in_buttons[13]},
	{"_button14",				&in_buttons[14]},
	{"_button15",				&in_buttons[15]},
	{"_button16",				&in_buttons[16]},
	{"_button17",				&in_buttons[17]},
	{"_button18",				&in_buttons[18]},
	{"_button19",				&in_buttons[19]},
	{"_button20",				&in_buttons[20]},
	{"_button21",				&in_buttons[21]},
	{"_button22",				&in_buttons[22]},
	{"_button23",				&in_buttons[23]},
	{"_button24",				&in_buttons[24]},
	{"_button25",				&in_buttons[25]},
	{"_button26",				&in_buttons[26]},
	{"_button27",				&in_buttons[27]},
	{"_button28",				&in_buttons[28]},
	{"_button29",				&in_buttons[29]},
	{"_button30",				&in_buttons[30]},
	{"_button31",				&in_buttons[31]},
	{NULL,						NULL}
};

keyButton_t					in_moveForward;
keyButton_t					in_moveBack;
keyButton_t					in_moveRight;
keyButton_t					in_moveLeft;
keyButton_t					in_moveUp;
keyButton_t					in_moveDown;
keyButton_t					in_turnRight;
keyButton_t					in_turnLeft;
keyButton_t					in_lookUp;
keyButton_t					in_lookDown;
keyButton_t					in_speed;
keyButton_t					in_strafe;
keyButton_t					in_voiceChat;
keyButton_t					in_buttons[KB_MAX];


/*
 ==================
 IN_KeyButtonDown
 ==================
*/
void IN_KeyButtonDown (keyButton_t *kb, int key, int time){

	if (kb->keys[0] == key || kb->keys[1] == key)
		return;		// Repeating key

	if (!kb->keys[0])
		kb->keys[0] = key;
	else if (!kb->keys[1])
		kb->keys[1] = key;
	else {
		Com_Printf("Three keys down for a button!\n");
		return;
	}

	if (kb->active)
		return;		// Already down

	// Save timestamp
	kb->downTime = time;

	kb->active = true;
	kb->wasPressed = true;

	kb->toggled = !kb->toggled;

	// Special case for push-to-talk voice chat mode
	if (kb == &in_voiceChat && cl_voiceChatMode->integerValue == 1)
		S_StartVoiceCapture();
}

/*
 ==================
 IN_KeyButtonUp
 ==================
*/
void IN_KeyButtonUp (keyButton_t *kb, int key, int time){

	if (kb->keys[0] == key)
		kb->keys[0] = 0;
	else if (kb->keys[1] == key)
		kb->keys[1] = 0;
	else
		return;		// Key up without corresponding down (GUI pass through)

	if (kb->keys[0] || kb->keys[1])
		return;		// Some other key is still holding it down

	if (!kb->active)
		return;		// Already up (this should not happen)

	// Save timestamp
	kb->upTime = time;

	kb->active = false;

	// Special case for push-to-talk voice chat mode
	if (kb == &in_voiceChat && cl_voiceChatMode->integerValue == 1)
		S_StopVoiceCapture(false);
}

/*
 ==================
 IN_KeyButtonState
 ==================
*/
float IN_KeyButtonState (keyButton_t *kb){

	float	frac;
	int		msec;

	if (kb->active){
		// Still down
		msec = com_frameTime - kb->downTime;

		kb->downTime = com_frameTime;
	}
	else {
		// Released
		msec = kb->upTime - kb->downTime;

		kb->downTime = 0;
		kb->upTime = 0;
	}

	frac = (float)msec / com_frameMsec;

	return glqMath::ClampFloat(frac, 0.0f, 1.0f);
}

/*
 ==================
 IN_KeyButtonForBinding
 ==================
*/
keyButton_t *IN_KeyButtonForBinding (const char *binding){

	keyButtonCmd_t	*kbc;

	for (kbc = in_keyButtonCmds; kbc->name; kbc++){
		if (!Str_ICompare(kbc->name, binding))
			return kbc->keyButton;
	}

	return NULL;
}


/*
 ==============================================================================

 MOUSE MOVEMENT

 ==============================================================================
*/


/*
 ==================
 IN_ClearMouseMoves
 ==================
*/
void IN_ClearMouseMoves (){

	int		i;

	cl.mouseIndex = 0;

	for (i = 0; i < MAX_MOUSE_SAMPLES; i++){
		cl.mouseX[i] = 0.0f;
		cl.mouseY[i] = 0.0f;
	}
}

/*
 ==================
 IN_MouseMove
 ==================
*/
void IN_MouseMove (int samples, float *x, float *y){

	float	weight;
	int		index;
	int		i;

	*x = 0.0f;
	*y = 0.0f;

	for (i = 0, weight = 1.0f; i < samples; i++, weight *= 0.5f){
		index = (MAX_MOUSE_SAMPLES - i + cl.mouseIndex) & (MAX_MOUSE_SAMPLES - 1);

		*x += cl.mouseX[index] * weight;
		*y += cl.mouseY[index] * weight;
	}

	cl.mouseIndex = (cl.mouseIndex + 1) & (MAX_MOUSE_SAMPLES - 1);

	cl.mouseX[cl.mouseIndex] = 0.0f;
	cl.mouseY[cl.mouseIndex] = 0.0f;
}

/*
 ==================
 IN_MouseMoveEvent
 ==================
*/
void IN_MouseMoveEvent (float x, float y){

	// If the console is active, do nothing
	if (Key_GetCatchers() & KEYCATCH_CONSOLE)
		return;

	// Send the mouse move event to the appropriate handler
	if (Key_GetCatchers() & KEYCATCH_GUI){
		GUI_MouseMove(x, y);
		return;
	}

	// If not active or playing a demo, do nothing
	if (cls.state != CS_ACTIVE || clc.demoPlaying)
		return;

	cl.mouseX[cl.mouseIndex] = x;
	cl.mouseY[cl.mouseIndex] = y;
}


/*
 ==============================================================================

 CONTROLLER AXES

 ==============================================================================
*/


/*
 ==================
 IN_ClearControllerAxes
 ==================
*/
void IN_ClearControllerAxes (){

	int		i;

	for (i = 0; i < MAX_CONTROLLER_AXES; i++)
		cl.controllerAxis[i] = 0.0f;
}

/*
 ==================
 IN_ControllerAxis
 ==================
*/
float IN_ControllerAxis (int axis, float deadZone, float saturation, float power, float scale){

	float	value;

	if (axis < 0 || axis >= MAX_CONTROLLER_AXES)
		return 0.0f;

	value = glqMath::FAbs(cl.controllerAxis[axis]);

	if (value <= deadZone)
		return 0.0f;

	if (value >= saturation){
		if (cl.controllerAxis[axis] < 0.0f)
			return -scale;

		return scale;
	}

	value = glqMath::Pow((value - deadZone) / (saturation - deadZone), power) * scale;

	if (cl.controllerAxis[axis] < 0.0f)
		return -value;

	return value;
}

/*
 ==================
 IN_ControllerAxisEvent
 ==================
*/
void IN_ControllerAxisEvent (int axis, float value){

	// If any key catcher is active, do nothing
	if (Key_GetCatchers())
		return;

	// If not active or playing a demo, do nothing
	if (cls.state != CS_ACTIVE || clc.demoPlaying)
		return;

	if (axis < 0 || axis >= MAX_CONTROLLER_AXES)
		return;

	cl.controllerAxis[axis] = glqMath::ClampFloat(value, -1.0f, 1.0f);
}


// ============================================================================


/*
 ==================
 IN_ClearStates
 ==================
*/
void IN_ClearStates (){

	// Clear mouse and controller device states
	IN_ClearDeviceStates();

	// Clear all key buttons
	Mem_Fill(&in_moveForward, 0, sizeof(keyButton_t));
	Mem_Fill(&in_moveBack, 0, sizeof(keyButton_t));
	Mem_Fill(&in_moveRight, 0, sizeof(keyButton_t));
	Mem_Fill(&in_moveLeft, 0, sizeof(keyButton_t));
	Mem_Fill(&in_moveUp, 0, sizeof(keyButton_t));
	Mem_Fill(&in_moveDown, 0, sizeof(keyButton_t));
	Mem_Fill(&in_turnRight, 0, sizeof(keyButton_t));
	Mem_Fill(&in_turnLeft, 0, sizeof(keyButton_t));
	Mem_Fill(&in_lookUp, 0, sizeof(keyButton_t));
	Mem_Fill(&in_lookDown, 0, sizeof(keyButton_t));
	Mem_Fill(&in_speed, 0, sizeof(keyButton_t));
	Mem_Fill(&in_strafe, 0, sizeof(keyButton_t));
	Mem_Fill(&in_voiceChat, 0, sizeof(keyButton_t));
	Mem_Fill(in_buttons, 0, KB_MAX * sizeof(keyButton_t));
}

/*
 ==================
 IN_Activate

 Called when the main window gains or loses focus.
 The window may have been destroyed and recreated between a deactivate and an
 activate.
 ==================
*/
void IN_Activate (bool active){

	if (active)
		IN_ActivateDevices();
	else
		IN_DeactivateDevices();
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 IN_Restart_f
 ==================
*/
static void IN_Restart_f (){

	IN_Shutdown();
	IN_Init();
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
void IN_Init (){

	Com_Printf("-------- Input Initialization --------\n");

	// Register variables
	in_alwaysRun = CVar_Register("in_alwaysRun", "0", CVAR_BOOL, CVAR_ARCHIVE, "Always run", 0, 0);	
	in_toggleRun = CVar_Register("in_toggleRun", "0", CVAR_BOOL, CVAR_ARCHIVE, "Toggle run on/off when pressing speed key", 0, 0);	
	in_toggleZoom = CVar_Register("in_toggleZoom", "0", CVAR_BOOL, CVAR_ARCHIVE, "Toggle zoom on/off when pressing zoom key", 0, 0);	
	in_angleSpeedKey = CVar_Register("in_angleSpeedKey", "1.5", CVAR_FLOAT, CVAR_ARCHIVE, "Angle change scale when holding down speed key", 0.0f, 10.0f);	
	in_pitchSpeed = CVar_Register("in_pitchSpeed", "140.0", CVAR_FLOAT, CVAR_ARCHIVE, "Pitch change speed when holding down look up/down keys", 0.0f, 360.0f);
	in_yawSpeed = CVar_Register("in_yawSpeed", "140.0", CVAR_FLOAT, CVAR_ARCHIVE, "Yaw change speed when holding down turn right/left keys", 0.0f, 360.0f);	
	in_freeLook = CVar_Register("in_freeLook", "1", CVAR_BOOL, CVAR_ARCHIVE, "Look around with mouse and controller", 0, 0);	
	in_forwardSpeed = CVar_Register("in_forwardSpeed", "200.0", CVAR_FLOAT, CVAR_ARCHIVE, "Forward movement speed", 0.0f, 0.0f);	
	in_upSpeed = CVar_Register("in_upSpeed", "200.0", CVAR_FLOAT, CVAR_ARCHIVE, "Up movement speed", 0.0f, 0.0f);
	in_sideSpeed = CVar_Register("in_sideSpeed", "200.0", CVAR_FLOAT, CVAR_ARCHIVE, "Side movement speed", 0.0f, 0.0f);
	in_mouse = CVar_Register("in_mouse", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_LATCH, "Enable mouse input", 0, 0);
	in_controller = CVar_Register("in_controller", "0", CVAR_BOOL, CVAR_ARCHIVE | CVAR_LATCH, "Enable controller input", 0, 0);

	m_showRate = CVar_Register("m_showRate", "0", CVAR_BOOL, CVAR_CHEAT, "Show mouse rate", 0, 0);
	m_smooth = CVar_Register("m_smooth", "1", CVAR_INTEGER, CVAR_ARCHIVE, "Number of samples blended for mouse viewing", 1, MAX_MOUSE_SAMPLES);
	m_sensitivity = CVar_Register("m_sensitivity", "5.0", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse view sensitivity", 0.0f, 100.0f);
	m_accel = CVar_Register("m_accel", "0.0", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse acceleration", 0.0f, 10.0f);
	m_forward = CVar_Register("m_forward", "1.0", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse forward/back speed", 0.0f, 0.0f);
	m_side = CVar_Register("m_side", "1.0", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse right/left speed", 0.0f, 0.0f);
	m_pitch = CVar_Register("m_pitch", "0.022", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse pitch scale", 0.0f, 0.0f);
	m_yaw = CVar_Register("m_yaw", "0.022", CVAR_FLOAT, CVAR_ARCHIVE, "Mouse yaw scale", 0.0f, 0.0f);

	c_deviceName = CVar_Register("c_deviceName", "", CVAR_STRING, CVAR_ARCHIVE | CVAR_LATCH, "Controller device name", 0, 0);

	// Add commands
	Cmd_AddCommand("restartInput", IN_Restart_f, "Restarts the input subsystem", NULL);

	// Initialize mouse and controller devices
	IN_InitDevices();

	Com_Printf("--------------------------------------\n");
}

/*
 ==================
 IN_Shutdown
 ==================
*/
void IN_Shutdown (){

	// Remove commands
	Cmd_RemoveCommand("restartInput");

	// Shutdown mouse and controller devices
	IN_ShutdownDevices();
}